TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { notFound } from 'next/navigation';4import { feeScheduleFor } from '@rareindex/valuation';5import { PageHeader } from '@/components/ui/page-header';6import { Card, CardHeader, EmptyState, StatStrip } from '@/components/ui/primitives';7import { LotsIntelCards, LotsIntelTable } from '@/components/market/lots-table';8import { SalesCards, SalesTable } from '@/components/market/sales-table';9import { FeeScheduleTable, FeeConfidenceBadge } from '@/components/market/fee-schedule';10import { getAuctionHouseStats, listAuctionHouses, listHouseResults, listLots } from '@/lib/queries/market-lists';11import { resolveHouse } from '@/lib/auction-house';12import { fmtMoney, fmtNum, fmtPct, fmtRelative } from '@/lib/format';1314export const revalidate = 300;1516export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {17 const { slug } = await params;18 const h = resolveHouse(slug, await listAuctionHouses());19 return h ? { title: `${h.house} — auctions, results & buyer's premium`, description: `${h.house} on RareIndex: open lots with all-in cost vs RIV, recorded results and the buyer's premium schedule used for all-in estimates.` } : { title: 'Auction house' };20}2122export default async function AuctionHousePage({ params }: { params: Promise<{ slug: string }> }) {23 const { slug } = await params;24 const houses = await listAuctionHouses();25 const h = resolveHouse(slug, houses);26 if (!h) notFound();27 const [stats, lots, results] = await Promise.all([getAuctionHouseStats(h.house), listLots({ house: h.house, open: true, limit: 60, sort: 'ending' }), listHouseResults(h.house, 50)]);28 const schedule = feeScheduleFor(h.house);29 return (30 <div>31 <PageHeader32 kicker="Auction house"33 crumbs={[{ label: 'Auctions', href: '/auctions' }, { label: h.house }]}34 title={h.house}35 description="Objective metrics only (§129): lots tracked, results recorded, median and record all-in sale, and the buyer's premium schedule RareIndex uses to turn hammer prices into buyer-pays costs. Sell-through is shown only when the house's results are recorded per lot."36 compact37 meta={38 <>39 <Link href={`/auctions?house=${encodeURIComponent(h.house)}`} className="hover:text-fg">40 Open lots →41 </Link>42 <Link href={`/auctions/calendar?house=${encodeURIComponent(h.house)}`} className="hover:text-fg">43 Calendar →44 </Link>45 </>46 }47 />48 <StatStrip49 className="mb-4"50 items={[51 { label: 'Open lots', value: fmtNum(stats?.openLots ?? h.upcoming), sub: stats ? `${fmtNum(stats.liveLots)} live · ${fmtNum(stats.lotsWithBids)} with bids` : undefined },52 { label: 'Below RIV', value: stats?.lotsAssessed ? fmtNum(stats.lotsBelowRiv) : '—', sub: stats?.lotsAssessed ? `of ${fmtNum(stats.lotsAssessed)} assessed` : 'not assessed yet' },53 { label: 'Results recorded', value: fmtNum(stats?.results ?? 0), sub: stats?.lastResultAt ? `last ${fmtRelative(stats.lastResultAt)}` : undefined },54 { label: 'Median all-in · 1Y', value: stats?.medianAllInUsd365d !== null && stats?.medianAllInUsd365d !== undefined ? fmtMoney(stats.medianAllInUsd365d) : '—', sub: stats?.results365d ? `${fmtNum(stats.results365d)} results` : 'no result in 12 months' },55 { label: 'Record all-in · 1Y', value: stats?.maxAllInUsd365d !== null && stats?.maxAllInUsd365d !== undefined ? fmtMoney(stats.maxAllInUsd365d) : '—' },56 { label: 'Sell-through', value: stats?.sellThrough !== null && stats?.sellThrough !== undefined ? fmtPct(stats.sellThrough, 0, false) : '—', sub: stats?.sellThrough !== null && stats?.sellThrough !== undefined ? 'lots sold / lots ended' : 'not derivable from this source' },57 ]}58 />59 <div className="grid gap-4 xl:grid-cols-[2fr_1fr]">60 <div className="space-y-4">61 <Card className="overflow-hidden">62 <CardHeader title="Open lots" subtitle="All-in = hammer + buyer's premium (schedule below); bid vs RIV gated like listings." action={<Link href={`/auctions?house=${encodeURIComponent(h.house)}`} className="text-muted hover:text-fg">All →</Link>} />63 <div className="md:hidden">64 <LotsIntelCards items={lots} />65 {!lots.length ? <EmptyState title="No open lots" compact /> : null}66 </div>67 <div className="hidden md:block">68 <LotsIntelTable items={lots} showHouse={false} emptyTitle="No open lots" emptyDescription="This house has no live or upcoming lot tracked right now." />69 </div>70 </Card>71 <Card className="overflow-hidden">72 <CardHeader title="Recent results" subtitle={stats?.estimatedPremiumShare !== null && stats?.estimatedPremiumShare !== undefined ? `Recorded sales from this house. Premium estimated on ${fmtPct(stats.estimatedPremiumShare, 0, false)} of results with a fee basis; the rest report the premium as included or none.` : 'Recorded sales from this house; fee basis appears once the fees worker has run.'} />73 <div className="md:hidden">74 <SalesCards items={results} />75 </div>76 <div className="hidden md:block">77 <SalesTable items={results} />78 </div>79 {!results.length ? <EmptyState title="No results recorded" description="Results flow into Sales once a result connector runs for this house." compact /> : null}80 </Card>81 </div>82 <Card className="overflow-hidden">83 <CardHeader title="Buyer's premium" subtitle="Used to compute all-in costs (§35)" />84 <div className="p-4">85 {schedule ? (86 <FeeScheduleTable schedule={schedule} />87 ) : (88 <div className="space-y-2 text-[12px] text-muted">89 <FeeConfidenceBadge confidence="default" />90 <p>No schedule on file for this house. When a result is recorded as hammer-only, RareIndex adds a default 22 % premium and labels the all-in figure “premium added (default)”; when the connector does not state whether the premium is included, the price is shown as recorded with “fees unknown”.</p>91 <p className="text-subtle">92 Schedules live in <code className="mono-num">data/fees/auction-houses.json</code> — see the <Link href="/methodology#fees" className="underline-offset-2 hover:underline">methodology</Link>.93 </p>94 </div>95 )}96 </div>97 </Card>98 </div>99 </div>100 );101}102